package server

import (
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"

	"github.com/codegangsta/inject"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"

	"mychat/internal/common/cmctx"
	"mychat/internal/common/errors"
	"mychat/internal/common/logger"
)

type response struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Payload any    `json:"payload"`
}

func GenHandler(lg logger.Logger, req any, servicefunc any) gin.HandlerFunc {
	lg = lg.WithCustomTag(logger.LoggerTagHTTP)
	return func(gtx *gin.Context) {
		injector := inject.New()
		injector.Map(gtx.Request.Context())

		if req != nil {
			reqstr, ok := req.(string)
			if ok {
				injector.Map(gtx.Param(reqstr))
			} else {
				var err error
				var reqV any

				reqType := reflect.TypeOf(req).Elem()
				reqV = reflect.New(reqType).Interface()
				method := gtx.Request.Method
				if method == "GET" || method == "HEAD" {
					err = gtx.ShouldBindQuery(reqV)
				} else {
					err = gtx.ShouldBindBodyWith(reqV, binding.JSON)
				}

				if err != nil {
					lg.ErrorCtx(gtx, "bind request failed||method=%s||requestType=%s||req=%+v||err=%v",
						method, reqType.Name(), reqV, err)
					cmctx.ReplyJsonAPIError(gtx, &errors.ParamMissingError)
					return
				}
				injector.Map(reqV)
			}
		}

		retlist, err := injector.Invoke(servicefunc)
		if err != nil {
			lg.ErrorCtx(gtx, "injector.Invoke failed||err=%v", err)
			cmctx.ReplyJsonAPIError(gtx, &errors.DefaultError)
			return
		}

		respVal, respErr := retlist[0].Interface(), retlist[1].Interface()
		if e, ok := respErr.(*errors.Error); ok {
			if e != nil {
				cmctx.ReplyJsonAPIError(gtx, e)
				return
			}

			buf, err := json.Marshal(response{
				Code:    0,
				Message: "success",
				Payload: respVal,
			})
			if err != nil {
				lg.ErrorCtx(gtx, "json marshal response failed||err=%v", err)
				cmctx.ReplyJsonAPIError(gtx, &errors.DefaultError)
				return
			}
			gtx.Data(http.StatusOK, "application/json;charset=utf-8", buf)
			return
		} else {
			err := fmt.Sprintf("sevice resp error type is not *errors.Error||err=%+v", err)
			panic(err)
		}
	}
}
